home *** CD-ROM | disk | FTP | other *** search
/ PsL Monthly 1993 December / PSL Monthly Shareware CD-ROM (December 1993).iso / prgmming / dos / pascal / compmrk.com / COMPMARK.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-12-27  |  20.2 KB  |  591 lines

  1. {$A+,B-,D-,E-,F-,I-,L-,N-,O-,R-,S-,V+}
  2. Unit CompMark;
  3. { COMPMARK.PAS - Adaptive data compression using "Splay" tree with Markov
  4.   model.  This algorithm was originally implemented in Pascal on the IBM PC
  5.   by Kim Kokkonen [72457,2131], TurboPower Software, 8-16-88.  His
  6.   documentation follows:
  7.  
  8.   "Based on an article by Douglas W. Jones, 'Application of Splay Trees to
  9.   Data Compression', in Communications of the ACM, August 1988, page 996.
  10.  
  11.   "This is a method somewhat similar to Huffman encoding (SQZ), but which is
  12.   locally adaptive. It therefore requires only a single pass over the
  13.   uncompressed file, and does not require storage of a code tree with the
  14.   compressed file. It is characterized by code simplicity and low data
  15.   overhead. Compression efficiency is not as good as recent ARC
  16.   implementations, especially for large files. However, for small files, the
  17.   efficiency of SPLAY approaches that of ARC's squashing technique."
  18.  
  19.   I have re-implemented the algorithm in assembler with some changes:
  20.  
  21.   1. My intended use for this unit is to compress a relatively small data
  22.      buffer as one might wish to do before transmitting it over a
  23.      communications channel or storing it on disk.  Consequently, this unit
  24.      compresses and decompresses an in-memory buffer rather than a file.
  25.      InitCompress initially balances the Splay tree[s] in the work area.
  26.      The work area retains any tree adaptations done during compression or
  27.      expansion until InitCompress is called again.  Therefore, If you wish to
  28.      make each buffer independently expandable, you must call InitCompress
  29.      before each call to CompressData.  ExpandData will detect what you have
  30.      done and automatically call InitCompress where necessary.
  31.  
  32.   2. I run-length-encode the input before compressing it with the Splay
  33.      tree algorithm.  This improves the compression ratio where the input
  34.      contains long runs of duplicate characters.
  35.  
  36.   3. Kim's original implementation used a unique trailer character to
  37.      mark the end of data.  I store the pre-compressed data length as
  38.      the first word in the compressed data buffer and do not use a
  39.      unique trailer character.  This permits the uncompressed length to be
  40.      determined by inspection and, because the ExpandBuffer routine stops
  41.      when the output length is achieved, transmission errors will be less
  42.      likely to blow out a buffer on the receiving end.  The "Bits" parameter
  43.      from InitCompress is also stored as the third byte in the buffer.
  44.  
  45.   4. I have implemented the "Markov modeling" technique outlined in the Jones
  46.      ACM reference.  You may (indirectly) indicate the number of states in
  47.      the InitCompress procedure.  The work area size requirements are outlined
  48.      in the comments on that proc.  InitCompress(0) should reproduce the
  49.      compression behavior of Kim's original SPLAY.PAS.  The work area is
  50.      passed as a parameter to the assembler primatives so they may be fully
  51.      re-entrant.
  52.  
  53.   5. I have added objects for management of compressed sequential record
  54.      files on disk (see below).
  55.  
  56.   Cautions:
  57.  
  58.   1. CompressData and ExpandData both read/write their input/output under the
  59.      constraints of the 8086 segmented archetecture and I do not normalize
  60.      the input/output pointers before starting.  Therefore, you should call
  61.      these routines with normalized pointers, and expect invalid output if the
  62.      input/output length exceeds 64k minus Ofs(Dest).
  63.  
  64.   2. The compressed output data may actually be longer than the input data
  65.      if the input already has high "entropy".  Compression typically increases
  66.      the data entropy.  Multiple compressions, therefore, are usually a waste
  67.      of time.
  68.  
  69.   3. As indicated in the ACM reference, this compression technique does not
  70.      perform as well as LZW and its variations on large text files.  It should
  71.      be considered only where working storage is very scarce and/or the data
  72.      to be compressed is expected to contain considerable redundency at the
  73.      character level.  The reference indicates that this algorithm can do
  74.      especially well with image files.
  75.  
  76.   This program is contributed to the public domain.
  77.   Please report bugs to the author:
  78.  
  79.   Edwin T. Floyd [76067,747]
  80.   #9 Adams Park Ct.
  81.   Columbus, GA 31909
  82.   404-576-3305 (work)
  83.   404-322-0076 (home)
  84.  
  85.   History
  86.   --------
  87.   12-27-89 Added compressed sequential file objects
  88.   12-07-89 Added 'cld' to compmark.asm, added auto-init detection logic
  89.   10-15-89 Initial Upload
  90.  
  91. }
  92. Interface Uses DOS;
  93.  
  94. Type
  95.   { High-level objects for compressed sequential file support. }
  96.  
  97.   CompFileBase = Object
  98.   { Used by objects below - Don't instantiate this object }
  99.     CompBuff : Pointer; { Pointer to I/O buffer }
  100.     CompTree : Pointer; { Pointer to compression/expansion work area }
  101.     CompTrLen : LongInt;{ Length of compression/expansion work area }
  102.     CompTotal : LongInt;{ Total size of uncompressed data in file }
  103.     CompPosn : Word;    { Current position in I/O buffer }
  104.     CompBufSize : Word; { I/O buffer size }
  105.     CompFile : File;    { Physical file }
  106.     CompName : PathStr; { File name }
  107.     CompOpen : Boolean; { True if file is open }
  108.     CompBits : Byte;    { Current bits value }
  109.     Constructor Init;   { Dummy constructor, aborts program }
  110.     Destructor Done; Virtual; { Close file and release buffer and work area }
  111.   End;
  112.  
  113.   CompFileIn = Object(CompFileBase)
  114.   { Compressed sequential input file }
  115.     CompBytes : Word;   { Number of bytes currently in buffer }
  116.     Constructor Init(Name : PathStr; BufSize : Word);
  117.       { Name specifies an existing compressed sequential file.  BufSize
  118.         specifies the size of I/O buffer to obtain from the heap.  File is
  119.         initially positioned to the first record. }
  120.     Procedure GetRecord(Var Rec; Len : Word);
  121.       { Uncompress the current record into Rec for a maximum length of Len
  122.         bytes and update the file position to the next record. }
  123.     Function RecLength : Word;
  124.       { Returns the uncompressed length in bytes of the current record (this
  125.         is the length of the record to be returned by the next GetRecord). }
  126.     Function Eof : Boolean;
  127.       { Returns TRUE after the last record has been retrieved by GetRecord. }
  128.     Procedure Rewind;
  129.       { Call this at any time to restart at the first record. }
  130.   End;
  131.  
  132.   CompFileOut = Object(CompFileBase)
  133.   { Compressed sequential output file }
  134.     CompFlushed : Boolean; { True if output file doesn't need flushing }
  135.     Constructor Init(Name : PathStr; BufSize : Word);
  136.       { Name specifies the compressed sequential file to be created.  BufSize
  137.         specifies the size of buffer to obtain from the heap.  After Init,
  138.         the file is empty and ready to receive the first record.  As a rule,
  139.         BufSize should be AT LEAST 1.25 * SizeOf(Largest_Rec) + 5.  To specify
  140.         the 'Bits' value to be used for compression, call InitCompress
  141.         immediately before the call to this constructor, otherwise Bits=0. }
  142.     Destructor Done; Virtual;
  143.       { Flush any remaining records in the buffer to the file, close the
  144.         the file and release the buffer. }
  145.     Procedure PutRecord(Var Rec; Len : Word);
  146.       { Compress Rec for Len bytes and write to the file. }
  147.     Procedure Flush;
  148.       { Flush any records in the buffer to the file, close the file, re-open,
  149.         and position to end of file. }
  150.   End;
  151.  
  152.   CompFileAppend = Object(CompFileOut)
  153.   { Append to existing compressed sequential file }
  154.     Constructor Init(Name : PathStr; BufSize : Word);
  155.       { Name and BufSize as above.  After this Init, if the file already
  156.         exists, it is positioned at the end of file ready to receive the next
  157.         record.  If the file doesn't exist, a new file is created, as for
  158.         CompFileOut.  Specify the 'Bits' value as above; bits may be different
  159.         from the value originally specified. }
  160.   End;
  161.  
  162. { Low-level routines for buffer compression/expansion. }
  163.  
  164. Procedure InitCompress(Bits : Byte);
  165. { Allocate compression/expansion  work area and initialize.  "Bits" refers to
  166.   the number of bits in the current plain-text byte that determine the "state"
  167.   of the Markov model to use for the next byte.  "Bits" may be any value from
  168.   0 to 8.  The size of the work area is determined by the number of states that
  169.   may be specified by the indicated number of bits (plus 16 bytes).  Each state
  170.   is a Splay tree which occupies 1.5K of memory, so e.g, Bits=0 => determines
  171.   1 tree, or 1536+16 for a work area size of 1552 bytes.  Bits=2 determines 4
  172.   trees or 4*1536+16 for a 6160 byte work area.   Bits=8 determines 256 trees
  173.   for a size of 393232 bytes.  In general, the larger the number of states,
  174.   the better the compression.   If InitCompress is not called, CompressData
  175.   will call it with Bits = 0, and ExpandData will call it with the same "Bits"
  176.   setting used on the compressed buffer.  InitCompress allocates its work area
  177.   with HugeGetMem from the public domain TPALLOC unit by Brian Foley of
  178.   TurboPower Software. }
  179.  
  180. Function WorkAreaSize(Bits : Byte) : LongInt;
  181. { This function returns the length in bytes of the work area that would be
  182.   allocated by InitCompress with the indicated Bits setting. }
  183.  
  184. Function CompressData(Var Source; Count : Word; Var Dest) : Word;
  185. { Compress Count bytes from Source and place the compressed data in Dest.
  186.   The length of the compressed data is returned as the function result. }
  187.  
  188. Function ExpandData(Var Source; Var Dest) : Word;
  189. { Expand compressed data from Source and place the expanded data in Dest.
  190.   The length of the expanded data is returned as the function result. }
  191.  
  192. Function ExpandedLength(Var Source) : Word;
  193. { Inspect the compressed data in Source and return the length it will have
  194.   when expanded. }
  195.  
  196. Procedure ExpandDataLimited(Var Source; Var Dest; Len : Word);
  197. { Expand compressed data from Source and place the expanded data in Dest.
  198.   Truncate the expanded data to no more than Len bytes. }
  199.  
  200. Implementation
  201. Uses TpAlloc;
  202.  
  203. Const
  204.   MagicNumber = $4295E8F6;
  205.   { This number marks the beginning of a compressed sequential file. }
  206.   ReadMode = $40;     { Deny None, Read access }
  207.   WriteMode = $42;    { Deny None, Read/Write access }
  208.  
  209. Type
  210.   BigArray = Array[0..65534] Of Byte;
  211.  
  212.   BufHeader = Record
  213.     BufLength : Word; { Length of un-compressed data }
  214.     BufBits : Byte;   { 'Bits' value used to compress this buffer }
  215.     BufData : Byte;   { Beginning of compressed data }
  216.   End;
  217.  
  218. Var
  219.   CompWork : Pointer; { Pointer to compression work area }
  220.   WorkSize : LongInt; { Work area size }
  221.   InitBits : Byte;    { 'Bits' value for current work area }
  222.  
  223. {$F-}
  224. Procedure InitSplay(Var Work; Bits : Word);
  225. External; { NEAR call }
  226.  
  227. Function CompressBuffer(Var Work; Var Source; Count : Word; Var Dest) : Word;
  228. External; { NEAR call }
  229.  
  230. Procedure ExpandBuffer(Var Work; Var Source; Var Dest; Count : Word);
  231. External; { NEAR call }
  232.  
  233. {$L COMPMARK.OBJ }
  234.  
  235. Function WorkAreaSize(Bits : Byte) : LongInt;
  236. Begin
  237.   If Bits > 8 Then Bits := 8;
  238.   WorkAreaSize := (LongInt(1) Shl Bits) * 1536 + 16;
  239. End;
  240.  
  241. Procedure InitCompress(Bits : Byte);
  242. Begin
  243.   Bits := Bits And $7F;
  244.   If Bits > 8 Then Bits := 8;
  245.   If Bits <> (InitBits And $7F) Then Begin
  246.     HugeFreeMem(CompWork, WorkSize);
  247.     WorkSize := WorkAreaSize(Bits);
  248.     HugeGetMem(CompWork, WorkSize);
  249.     If CompWork = Nil Then Begin
  250.       WriteLn('InitCompress is unable to allocate ', WorkSize,
  251.         ' bytes of workarea');
  252.       Halt(1);
  253.     End;
  254.     InitBits := Bits;
  255.   End;
  256.   InitSplay(CompWork^, Bits);
  257.   InitBits := InitBits Or $80;
  258. End;
  259.  
  260. Function CompressData(Var Source; Count : Word; Var Dest) : Word;
  261. Var
  262.   DestBuf : BufHeader Absolute Dest;
  263. Begin
  264.   If (InitBits And $7F) > 8 Then InitCompress(0);
  265.   With DestBuf Do Begin
  266.     BufLength := Count;
  267.     BufBits := InitBits;
  268.     InitBits := InitBits And $7F;
  269.     If Count > 0 Then
  270.       CompressData := CompressBuffer(CompWork^, Source, Count, BufData) + 3
  271.     Else CompressData := 3;
  272.   End;
  273. End;
  274.  
  275. Function ExpandData(Var Source; Var Dest) : Word;
  276. Var
  277.   SourceBuf : BufHeader Absolute Source;
  278. Begin
  279.   With SourceBuf Do Begin
  280.     If ((BufBits And $7F) <> (InitBits And $7F))
  281.     Or ((BufBits And $80) <> 0) Then InitCompress(BufBits);
  282.     If BufLength > 0 Then ExpandBuffer(CompWork^, BufData, Dest,
  283.       BufLength);
  284.     InitBits := InitBits And $7F;
  285.     ExpandData := BufLength;
  286.   End;
  287. End;
  288.  
  289. Procedure ExpandDataLimited(Var Source; Var Dest; Len : Word);
  290. Var
  291.   SourceBuf : BufHeader Absolute Source;
  292. Begin
  293.   With SourceBuf Do Begin
  294.     If ((BufBits And $7F) <> (InitBits And $7F))
  295.     Or ((BufBits And $80) <> 0) Then InitCompress(BufBits);
  296.     If Len > BufLength Then Len := BufLength;
  297.     If Len > 0 Then ExpandBuffer(CompWork^, BufData, Dest, Len);
  298.     InitBits := InitBits And $7F;
  299.   End;
  300. End;
  301.  
  302. Function ExpandedLength(Var Source) : Word;
  303. Var
  304.   SourceBuf : BufHeader Absolute Source;
  305. Begin
  306.   ExpandedLength := SourceBuf.BufLength;
  307. End;
  308.  
  309. Constructor CompFileBase.Init;
  310. Begin
  311.   WriteLn('Use CompFileIn or CompFileOut');
  312.   Halt(1);
  313. End;
  314.  
  315. Destructor CompFileBase.Done;
  316. Begin
  317.   If CompOpen Then Begin
  318.     Close(CompFile);
  319.     CompOpen := False;
  320.   End;
  321.   If CompBufSize > 0 Then Begin
  322.     FreeMem(CompBuff, CompBufSize);
  323.     CompBufSize := 0;
  324.   End;
  325.   If CompTrLen > 0 Then Begin
  326.     HugeFreeMem(CompTree, CompTrLen);
  327.     CompTree := Nil;
  328.     CompTrLen := 0;
  329.     CompBits := 255;
  330.   End;
  331. End;
  332.  
  333. Constructor CompFileIn.Init(Name : PathStr; BufSize : Word);
  334. Var
  335.   Magic : LongInt;
  336.   OldMode : Byte;
  337. Begin
  338.   CompOpen := False;
  339.   CompBufSize := 0;
  340.   CompBytes := 0;
  341.   CompTree := Nil;
  342.   CompTrLen := 0;
  343.   CompBits := 255;
  344.   CompPosn := 0;
  345.   CompName := FExpand(Name);
  346.   {$I-}
  347.   OldMode := FileMode;
  348.   FileMode := ReadMode;
  349.   Assign(CompFile, CompName);
  350.   Reset(CompFile, 1);
  351.   FileMode := OldMode;
  352.   {$I+}
  353.   If IoResult = 0 Then Begin
  354.     CompBufSize := BufSize;
  355.     GetMem(CompBuff, CompBufSize);
  356.     CompOpen := True;
  357.     BlockRead(CompFile, Magic, SizeOf(Magic));
  358.     BlockRead(CompFile, CompTotal, SizeOf(CompTotal));
  359.     BlockRead(CompFile, CompBuff^, CompBufSize, CompBytes);
  360.     If (Magic <> MagicNumber)
  361.     Or ((CompBytes > 0) And (Word(CompBuff^) + 2 > CompBytes))
  362.     Then Begin
  363.       WriteLn('Invalid compressed file format: ', CompName);
  364.       Halt(1);
  365.     End;
  366.   End;
  367. End;
  368.  
  369. Procedure CompFileIn.GetRecord(Var Rec; Len : Word);
  370. Var
  371.   SaveWork : Pointer;
  372.   SaveLen : LongInt;
  373.   WorkLen : Word;
  374.   SaveBits : Byte;
  375. Begin
  376.   SaveWork := CompWork;
  377.   SaveLen := WorkSize;
  378.   SaveBits := InitBits;
  379.   CompWork := CompTree;
  380.   WorkSize := CompTrLen;
  381.   InitBits := CompBits;
  382.   If CompBytes > 0 Then Begin
  383.     ExpandDataLimited(BigArray(CompBuff^)[CompPosn+2], Rec, Len);
  384.     Move(BigArray(CompBuff^)[CompPosn], WorkLen, 2);
  385.     CompPosn := CompPosn + WorkLen + 2;
  386.     Move(BigArray(CompBuff^)[CompPosn], WorkLen, 2);
  387.     If (CompPosn >= CompBytes) Or (WorkLen + CompPosn + 2 > CompBytes)
  388.     Then Begin
  389.       If CompPosn < CompBytes Then Begin
  390.         If CompPosn > 0 Then Begin
  391.           CompBytes := CompBytes - CompPosn;
  392.           Move(BigArray(CompBuff^)[CompPosn], CompBuff^, CompBytes);
  393.         End;
  394.       End Else CompBytes := 0;
  395.       CompPosn := 0;
  396.       If FilePos(CompFile) < FileSize(CompFile) Then Begin
  397.         BlockRead(CompFile, BigArray(CompBuff^)[CompBytes],
  398.           CompBufSize - CompBytes, WorkLen);
  399.         CompBytes := CompBytes + WorkLen;
  400.       End;
  401.       If (CompBytes > 0) And (Word(CompBuff^) + 2 > CompBytes) Then Begin
  402.         WriteLn('Invalid file format or buffer too short: ', CompName);
  403.         WriteLn('Expecting ', Word(CompBuff^) + 2, ' bytes');
  404.         WriteLn('Buffer holds ', CompBytes, ' bytes');
  405.         WriteLn(WorkLen, ' bytes from last file read');
  406.         WriteLn('File position is: ', FilePos(CompFile));
  407.         Halt(1);
  408.       End;
  409.     End;
  410.   End;
  411.   CompTree := CompWork;
  412.   CompTrLen := WorkSize;
  413.   CompBits := InitBits;
  414.   CompWork := SaveWork;
  415.   WorkSize := SaveLen;
  416.   InitBits := SaveBits;
  417. End;
  418.  
  419. Function CompFileIn.RecLength : Word;
  420. Begin
  421.   If CompBytes > 0 Then
  422.     RecLength := ExpandedLength(BigArray(CompBuff^)[CompPosn+2])
  423.   Else RecLength := 0;
  424. End;
  425.  
  426. Function CompFileIn.Eof : Boolean;
  427. Begin
  428.   Eof := CompBytes = 0;
  429. End;
  430.  
  431. Procedure CompFileIn.Rewind;
  432. Begin
  433.   If CompOpen Then Begin
  434.     Seek(CompFile, SizeOf(LongInt));
  435.     BlockRead(CompFile, CompTotal, SizeOf(CompTotal));
  436.     BlockRead(CompFile, CompBuff^, CompBufSize, CompBytes);
  437.     CompPosn := 0;
  438.   End;
  439. End;
  440.  
  441. Constructor CompFileOut.Init(Name : PathStr; BufSize : Word);
  442. Var
  443.   Magic : LongInt;
  444.   OldMode : Byte;
  445. Begin
  446.   CompBufSize := BufSize;
  447.   CompName := FExpand(Name);
  448.   OldMode := FileMode;
  449.   FileMode := WriteMode;
  450.   Assign(CompFile, CompName);
  451.   ReWrite(CompFile, 1);
  452.   FileMode := OldMode;
  453.   Magic := MagicNumber;
  454.   BlockWrite(CompFile, Magic, SizeOf(Magic));
  455.   CompTotal := 0;
  456.   BlockWrite(CompFile, CompTotal, SizeOf(CompTotal));
  457.   CompOpen := True;
  458.   GetMem(CompBuff, CompBufSize);
  459.   CompPosn := 0;
  460.   CompFlushed := True;
  461.   If (InitBits And $80) <> 0 Then Begin
  462.     CompTree := CompWork;
  463.     CompTrLen := WorkSize;
  464.     CompBits := InitBits;
  465.     CompWork := Nil;
  466.     WorkSize := 0;
  467.     InitBits := 255;
  468.   End Else Begin
  469.     CompTree := Nil;
  470.     CompTrLen := 0;
  471.     CompBits := 255;
  472.   End;
  473. End;
  474.  
  475. Destructor CompFileOut.Done;
  476. Begin
  477.   If CompPosn > 0 Then BlockWrite(CompFile, CompBuff^, CompPosn);
  478.   CompPosn := 0;
  479.   Seek(CompFile, SizeOf(LongInt));
  480.   BlockWrite(CompFile, CompTotal, SizeOf(CompTotal));
  481.   CompFileBase.Done;
  482. End;
  483.  
  484. Procedure CompFileOut.PutRecord(Var Rec; Len : Word);
  485. Var
  486.   WorkLen, CompLen : Word;
  487.   SaveWork : Pointer;
  488.   SaveLen : LongInt;
  489.   SaveBits : Byte;
  490. Begin
  491.   SaveWork := CompWork;
  492.   SaveLen := WorkSize;
  493.   SaveBits := InitBits;
  494.   CompWork := CompTree;
  495.   WorkSize := CompTrLen;
  496.   InitBits := CompBits;
  497.   WorkLen := CompBufSize - CompPosn;
  498.   If (Len + 5 > WorkLen) Or (Len + 5 > WorkLen - (Len Shr 2)) Then Begin
  499.     BlockWrite(CompFile, CompBuff^, CompPosn);
  500.     CompPosn := 0;
  501.     WorkLen := CompBufSize;
  502.   End;
  503.   CompLen := CompressData(Rec, Len, BigArray(CompBuff^)[CompPosn+2]);
  504.   If CompLen > WorkLen Then Begin
  505.     WriteLn('Fatal error - Buffer overflow');
  506.     Close(CompFile);
  507.     Halt(1);
  508.   End;
  509.   Inc(CompTotal, Len);
  510.   Move(CompLen, BigArray(CompBuff^)[CompPosn], 2);
  511.   CompPosn := CompPosn + CompLen + 2;
  512.   CompFlushed := False;
  513.   CompTree := CompWork;
  514.   CompTrLen := WorkSize;
  515.   CompBits := InitBits;
  516.   CompWork := SaveWork;
  517.   WorkSize := SaveLen;
  518.   InitBits := SaveBits;
  519. End;
  520.  
  521. Procedure CompFileOut.Flush;
  522. Var
  523.   OldMode : Byte;
  524. Begin
  525.   If Not CompFlushed Then Begin
  526.     If CompPosn > 0 Then BlockWrite(CompFile, CompBuff^, CompPosn);
  527.     CompPosn := 0;
  528.     Seek(CompFile, SizeOf(LongInt));
  529.     BlockWrite(CompFile, CompTotal, SizeOf(CompTotal));
  530.     Close(CompFile);
  531.     OldMode := FileMode;
  532.     FileMode := WriteMode;
  533.     Reset(CompFile, 1);
  534.     FileMode := OldMode;
  535.     Seek(CompFile, FileSize(CompFile));
  536.     CompFlushed := True;
  537.   End;
  538. End;
  539.  
  540. Constructor CompFileAppend.Init(Name : PathStr; BufSize : Word);
  541. Var
  542.   Magic : LongInt;
  543.   OldMode : Byte;
  544. Begin
  545.   CompName := FExpand(Name);
  546.   {$I-}
  547.   OldMode := FileMode;
  548.   FileMode := WriteMode;
  549.   Assign(CompFile, CompName);
  550.   Reset(CompFile, 1);
  551.   {$I+}
  552.   If IoResult = 0 Then Begin
  553.     BlockRead(CompFile, Magic, SizeOf(Magic));
  554.     If Magic <> MagicNumber Then Begin
  555.       WriteLn('Invalid compressed file format: ', CompName);
  556.       Halt(1);
  557.     End;
  558.     BlockRead(CompFile, CompTotal, SizeOf(CompTotal));
  559.     Seek(CompFile, FileSize(CompFile));
  560.   End Else Begin
  561.     ReWrite(CompFile, 1);
  562.     Magic := MagicNumber;
  563.     BlockWrite(CompFile, Magic, SizeOf(Magic));
  564.     CompTotal := 0;
  565.     BlockWrite(CompFile, CompTotal, SizeOf(CompTotal));
  566.   End;
  567.   FileMode := OldMode;
  568.   CompOpen := True;
  569.   CompBufSize := BufSize;
  570.   GetMem(CompBuff, CompBufSize);
  571.   CompPosn := 0;
  572.   CompFlushed := True;
  573.   If (InitBits And $80) <> 0 Then Begin
  574.     CompTree := CompWork;
  575.     CompTrLen := WorkSize;
  576.     CompBits := InitBits;
  577.     CompWork := Nil;
  578.     WorkSize := 0;
  579.     InitBits := 255;
  580.   End Else Begin
  581.     CompTree := Nil;
  582.     CompTrLen := 0;
  583.     CompBits := 255;
  584.   End;
  585. End;
  586.  
  587. Begin
  588.   CompWork := Nil;
  589.   WorkSize := 0;
  590.   InitBits := 255;
  591. End.